/**
 * 
 */
package com.ms.game.slot.base;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.infinispan.util.concurrent.ConcurrentHashSet;

import com.ms.game.gmtool.message.SGmtoolServerShutdownCountdownNotify;
import com.ms.game.gmtool.message.SGmtoolServerShutdownCountdownNotifyToClient;
import com.ms.service.module.broadcastlistener.LoginLogoutListener;
import com.xpec.c4.game.time.TimeConstants;
import com.xpec.c4.game.time.TimeSys;
import com.xpec.c4.game.utility.GUID;
import com.xpec.c4.game.utility.GameUtility;
import com.xpec.c4.message.MessageTypes;
import com.xpec.c4.service.h5server.H5ServerOperations;
import com.xpec.c4.service.message.MessageAction;
import com.xpec.c4.service.module.BasicServiceModule;
import com.xpec.c4.service.module.ModuleName;
import com.xpec.c4.service.module.ServerProperties;
import com.xpec.c4.service.module.common.PlayerCommonInfoManager;
import com.xpec.c4.service.server.CloseType;
import com.xpec.c4.service.server.ServerOperations;

/**
 * @author TW15010
 *
 * @param <T>
 *            座位歷史資訊資料結構
 * @param <T2>
 *            座位狀態資料結構
 */
public class DMAsyncHsitoryManger<T, T2> {
	
	
	private static final Log log = LogFactory.getLog(DMAsyncHsitoryManger.class);
	
	
	/**
	 * 廣播歷史資訊的計時器時間間隔
	 */
	public static final long BROADCAST_TIMER_INTERVAL = 1 * TimeConstants.MILLI_SECOND_PRE_SECOND;

	/**
	 * 下次廣播座位狀態更新的時間
	 */
	public static final long NEXT_BROADCAST_SEAT_STATUS_TIME = 1 * TimeConstants.MILLI_SECOND_PRE_SECOND;

	/**
	 * 下次廣播的時間亂數範圍起始
	 */
	public static final long NEXT_BROADCAST_TIME_RANDOM_BEG = 1 * TimeConstants.MILLI_SECOND_PRE_SECOND;

	/**
	 * 下次廣播的時間亂數範圍結束
	 */
	public static final long NEXT_BROADCAST_TIME_RANDOM_END = 2 * TimeConstants.MILLI_SECOND_PRE_SECOND;

	/**
	 * 要接收機台資料更新廣播的 session 列表<br>
	 * hall type \ gainRateID \ page \ session id
	 */
	private ConcurrentHashMap<String, ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, ConcurrentHashSet<GUID>>>> sessionList = new ConcurrentHashMap<String, ConcurrentHashMap<Integer,ConcurrentHashMap<Integer,ConcurrentHashSet<GUID>>>>();

	/**
	 * 用 session 反查他要收集哪些範圍的座位狀態更新<br>
	 * 用在角色斷線時可以快速的將他設定的範圍資訊從 {@link #sessionList} 清掉<br>
	 * session id \ watch info
	 */
	private HashMap<GUID, SessionWatchInfo> sessionWatchInfoMap = new HashMap<GUID, SessionWatchInfo>();

	/**
	 * 準備要廣播出去的機台歷史資料<br>
	 * hall type \ gainRateID \ page \ machineNum \ data
	 */
	private ConcurrentHashMap<String, ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, T>>>> simpleHistoryData = new ConcurrentHashMap<String, ConcurrentHashMap<Integer,ConcurrentHashMap<Integer,ConcurrentHashMap<Integer,T>>>>();

	/**
	 * 準備要廣播出去的機台狀態資料
	 * hall type \ gainRateID \ page \ machineNum \ data
	 */
	private ConcurrentHashMap<String, ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, T2>>>> simpleSeatStatusData = new ConcurrentHashMap<String, ConcurrentHashMap<Integer,ConcurrentHashMap<Integer,ConcurrentHashMap<Integer,T2>>>>();

	/**
	 * 下一次進行歷史資訊廣播的時間
	 */
	private long nextSendHistoryDataTime = 0;

	/**
	 * 下一次進行機台狀態廣播的時間
	 */
	private long nextSendSeatStatusTime = 0;

	/**
	 * 是否正在廣播歷史記錄
	 */
	private boolean sendHistoryWorking = false;

	/**
	 * 是否正在廣播座位狀態
	 */
	private boolean sendSeatStatusWorking = false;

	/**
	 * door man processor instance
	 */
	private IDoorManSeatUpdateBroadcaster dmProcessor;
	
	/**
	 * 運用此manager的module實體
	 */
	private BasicServiceModule ownModule;
	public void setOwnModule(BasicServiceModule ownModule){
		this.ownModule = ownModule;
	}
	
	public DMAsyncHsitoryManger(IDoorManSeatUpdateBroadcaster proc) {
		dmProcessor = proc;
	}

	/**
	 * 取得下次廣播座位更新資料的時間
	 * 
	 * @return
	 */
	private void updateNextSendHistoryDataTime() {
		long curTime = TimeSys.currentTimeMills();
		long randomTime = GameUtility.randomLong(NEXT_BROADCAST_TIME_RANDOM_BEG, NEXT_BROADCAST_TIME_RANDOM_END);

		nextSendHistoryDataTime = curTime + randomTime;
	}

	/**
	 * 取得下次廣播座位更新資料的時間
	 * 
	 * @return
	 */
	private void updateNextSendSeatStatusTime() {
		long curTime = TimeSys.currentTimeMills();

		nextSendSeatStatusTime = curTime + NEXT_BROADCAST_SEAT_STATUS_TIME;
	}

	/**
	 * 檢查是否到達廣播歷史資訊的時間
	 * 
	 * @return
	 */
	public boolean isSendHistoryDataTime() {
		if (TimeSys.currentTimeMills() >= nextSendHistoryDataTime) {
			updateNextSendHistoryDataTime();
			return true;
		}

		return false;
	}

	/**
	 * 檢查是否到達廣播座位狀態的時間
	 * 
	 * @return
	 */
	public boolean isSendSeatStatusTime() {
		if (TimeSys.currentTimeMills() >= nextSendSeatStatusTime) {
			updateNextSendSeatStatusTime();
			return true;
		}

		return false;
	}

	/**
	 * 新增一個要接收更新廣播的 session
	 * 
	 * @param hallType
	 * @param sessionId
	 * @param page
	 *            指定要收的座位列表頁數
	 */
	public void addSession(String hallType, GUID sessionId, int gainRateID, int pageFrom, int pageTo) {
		if (!GameUtility.isHaveString(hallType)) {
			return;
		}

		if (sessionId == null) {
			return;
		}

		// 找出這個連線的設定資料
		SessionWatchInfo tmpWatchInfo = sessionWatchInfoMap.get(sessionId);
		if (tmpWatchInfo != null) {
			// 取得之前指定的頁數
			int watchPageFrom = tmpWatchInfo.getPageFrom();
			int watchPageTo = tmpWatchInfo.getPageTo();
			String watchHallType = tmpWatchInfo.getHallType();
			int tmpGainRateID = tmpWatchInfo.getGainRateID();

			// 設定跟之前一樣就什麼都不做
			if (hallType.equals(watchHallType) && pageFrom == watchPageFrom && pageTo == watchPageTo && gainRateID == tmpGainRateID) {
				return;
			}
		}
		else{
			LoginLogoutListener.addLogOutSessionListener(ownModule, sessionId);
		}

		// 將指定的 館, session 之前的設定清除 (如果有的話)
		removeSession(sessionId);

		// 取得 page to session list
		ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, ConcurrentHashSet<GUID>>> gainRateToPageList = null;
		if(!sessionList.containsKey(hallType)){
			gainRateToPageList = new ConcurrentHashMap<Integer, ConcurrentHashMap<Integer,ConcurrentHashSet<GUID>>>();
			sessionList.put(hallType, gainRateToPageList);
		}
		else{
			gainRateToPageList = sessionList.get(hallType);
		}
		
		ConcurrentHashMap<Integer, ConcurrentHashSet<GUID>> pageToSessionList = null;
		if (!gainRateToPageList.containsKey(gainRateID)) {
			pageToSessionList = new ConcurrentHashMap<Integer, ConcurrentHashSet<GUID>>();
			gainRateToPageList.put(gainRateID, pageToSessionList);
		} else {
			pageToSessionList = gainRateToPageList.get(gainRateID);
		}

		// session 加到指定的頁數內
		for (int page = pageFrom; page <= pageTo; page++) {
			// 用 page 取得 session list
			ConcurrentHashSet<GUID> sessionSet = null;
			if (!pageToSessionList.containsKey(page)) {
				sessionSet = new ConcurrentHashSet<GUID>();
				pageToSessionList.put(page, sessionSet);
			} else {
				sessionSet = pageToSessionList.get(page);
			}

			// 將 session 加入 session list
			sessionSet.add(sessionId);
		}

		// 加入反查用的表
		SessionWatchInfo watchInfo = null;
		if (!sessionWatchInfoMap.containsKey(sessionId)) {
			watchInfo = new SessionWatchInfo();
			sessionWatchInfoMap.put(sessionId, watchInfo);
		} else {
			watchInfo = sessionWatchInfoMap.get(sessionId);
		}

		watchInfo.setHallType(hallType);
		watchInfo.setPageFrom(pageFrom);
		watchInfo.setPageTo(pageTo);
		watchInfo.setGainRateID(gainRateID);
	}
	
	/**
	 * 取得指定館內要接收廣播的 session list
	 * 
	 * @param hallType
	 * @param page
	 * @return
	 */
	public ConcurrentHashSet<GUID> getSessionList(String hallType, int gainRateID, int page) {
		ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, ConcurrentHashSet<GUID>>> gainRateToPageList
			= sessionList.get(hallType);
		if(gainRateToPageList == null){
			return null;
		}
		
		ConcurrentHashMap<Integer, ConcurrentHashSet<GUID>> pageToSessionList = gainRateToPageList.get(gainRateID);
		if (pageToSessionList == null) {
			return null;
		}

		ConcurrentHashSet<GUID> sessionSet = pageToSessionList.get(page);
		if (sessionSet == null) {
			return null;
		}

		return sessionSet;
	}

	/**
	 * 從所有的館別資料中把 session 移除
	 * 
	 * @param sessionId
	 */
	public void removeSession(GUID sessionId) {
		// 找出這個連線的設定資料
		SessionWatchInfo watchInfo = sessionWatchInfoMap.remove(sessionId);
		if (watchInfo != null) {
			// 取得之前指定的頁數
			int watchPageFrom = watchInfo.getPageFrom();
			int watchPageTo = watchInfo.getPageTo();
			String watchHallType = watchInfo.getHallType();
			int gainRateID = watchInfo.getGainRateID();

			// 從要接收更新廣播的連線列表中清除
			ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, ConcurrentHashSet<GUID>>> gainRateToPageList =
					sessionList.get(watchHallType);
			
			if(gainRateToPageList != null){
				ConcurrentHashMap<Integer, ConcurrentHashSet<GUID>> pageToSessionList = gainRateToPageList.get(gainRateID);
				if (pageToSessionList != null) {
					for (int watchPage = watchPageFrom; watchPage <= watchPageTo; watchPage++) {
						ConcurrentHashSet<GUID> sessionSet = pageToSessionList.get(watchPage);
						if (sessionSet.contains(sessionId)) {
							sessionSet.remove(sessionId);
						}
					} // for (int watchPage .....
				} // if (pageToSessionList != null) {
			}
			
			
		} // if (watchInfo != null) {
	}

	/**
	 * 廣播暫存的機台歷史資料給 client
	 * 
	 * @param messageType
	 */
	public void sendHistoryDataToClient(int messageType) {
		
		Set<String> hallSet = simpleHistoryData.keySet();

		// 掃所有的館
		for (String hallType : hallSet) {
			
			ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, T>>> gainRateIDtoPageList
				= simpleHistoryData.get(hallType);
			if (GameUtility.getMapSize(gainRateIDtoPageList) == 0) {
				continue;
			}
			
			Set<Integer> gainRateIDSet = gainRateIDtoPageList.keySet();
			
			// 掃所有的gainRateID
			for(Integer gainRateID : gainRateIDSet){
				
				ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, T>> pageToMachineDataMap = gainRateIDtoPageList.get(gainRateID);
				if (GameUtility.getMapSize(pageToMachineDataMap) == 0) {
					continue;
				}
				
				// 每一頁的資料
				for (Entry<Integer, ConcurrentHashMap<Integer, T>> entry : pageToMachineDataMap.entrySet()) {
					int page = entry.getKey();
					ConcurrentHashMap<Integer, T> machineDataMap = entry.getValue();

					if (GameUtility.getMapSize(machineDataMap) == 0) {
						continue;
					}

					// 取得要送出的資料
					Collection<T> dataList = machineDataMap.values();

					// 產生要送出的消息結構
					Object messageObj = dmProcessor
							.getSeatUpdateMessageInstance(messageType, (Collection<T>) dataList);

					// 取得發送對象 session list
					ConcurrentHashSet<GUID> sessionSet = getSessionList(hallType, gainRateID, page);
					
					if (GameUtility.getSetSize(sessionSet) > 0) {
						MessageAction.sendMessage(sessionSet, messageType, messageObj);
					}

					// 送出資料後將容器清空
					machineDataMap.clear();
				}
			}
		}
	}

	/**
	 * 廣播暫存的座位狀態資料給 client
	 * 
	 * @param messageType
	 */
	public void sendSeatStatusToClient(int messageType) {
		Set<String> hallSet = simpleSeatStatusData.keySet();

		// 掃所有的館
		for (String hallType : hallSet) {
			ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, T2>>> gainRateIDtoPageList
				= simpleSeatStatusData.get(hallType);
			if (GameUtility.getMapSize(gainRateIDtoPageList) == 0) {
				continue;
			}

			
			Set<Integer> gainRateIDSet = gainRateIDtoPageList.keySet();
			
			// 掃所有的gainRateID
			for(Integer gainRateID : gainRateIDSet){
				
				ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, T2>> pageToMachineDataMap = gainRateIDtoPageList.get(gainRateID);
				if (GameUtility.getMapSize(pageToMachineDataMap) == 0) {
					continue;
				}
				
				// 每一頁的資料
				for (Entry<Integer, ConcurrentHashMap<Integer, T2>> entry : pageToMachineDataMap.entrySet()) {
					int page = entry.getKey();
					ConcurrentHashMap<Integer, T2> machineDataMap = entry.getValue();

					if (GameUtility.getMapSize(machineDataMap) == 0) {
						continue;
					}

					// 取得要送出的資料
					Collection<T2> dataList = machineDataMap.values();

					// 產生要送出的消息結構
					Object messageObj = dmProcessor.getSeatUpdateMessageInstance(messageType,
							(Collection<T2>) dataList);

					// 取得發送對象 session list
					ConcurrentHashSet<GUID> sessionSet = getSessionList(hallType, gainRateID, page);

					if (GameUtility.getSetSize(sessionSet) > 0) {
						MessageAction.sendMessage(sessionSet, messageType, messageObj);
					}

					// 送出資料後將容器清空
					machineDataMap.clear();
				}
			}
		}
	}

	public void updateHistoryAsyncInfo(String halltype, int gainRateID, int page, int machineid, T value) {
		ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, T>>> gainRateIDToPageMap = null;
		if(!simpleHistoryData.containsKey(halltype)){
			gainRateIDToPageMap = new ConcurrentHashMap<Integer, ConcurrentHashMap<Integer,ConcurrentHashMap<Integer,T>>>();
			simpleHistoryData.put(halltype, gainRateIDToPageMap);
		}
		else{
			gainRateIDToPageMap = simpleHistoryData.get(halltype);
		}
		
		ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, T>> pageToMachineDataMap = null;
		if (!gainRateIDToPageMap.containsKey(gainRateID)) {
			pageToMachineDataMap = new ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, T>>();
			gainRateIDToPageMap.put(gainRateID, pageToMachineDataMap);
		} else {
			pageToMachineDataMap = gainRateIDToPageMap.get(gainRateID);
		}

		ConcurrentHashMap<Integer, T> machineDataMap = null;
		if (!pageToMachineDataMap.containsKey(page)) {
			machineDataMap = new ConcurrentHashMap<Integer, T>();
			pageToMachineDataMap.put(page, machineDataMap);
		} else {
			machineDataMap = pageToMachineDataMap.get(page);
		}

		machineDataMap.put(machineid, value);
	}

	public void updateSeatStatusAsyncInfo(String halltype, int gainRateID, int page, int machineid, T2 value) {
		ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, T2>>> gainRateIDToPageMap = null;
		if(!simpleSeatStatusData.containsKey(halltype)){
			gainRateIDToPageMap = new ConcurrentHashMap<Integer, ConcurrentHashMap<Integer,ConcurrentHashMap<Integer,T2>>>();
			simpleSeatStatusData.put(halltype, gainRateIDToPageMap);
		}
		else{
			gainRateIDToPageMap = simpleSeatStatusData.get(halltype);
		}
		
		ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, T2>> pageToMachineDataMap = null;
		if (!gainRateIDToPageMap.containsKey(gainRateID)) {
			pageToMachineDataMap = new ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, T2>>();
			gainRateIDToPageMap.put(gainRateID, pageToMachineDataMap);
		} else {
			pageToMachineDataMap = gainRateIDToPageMap.get(gainRateID);
		}

		ConcurrentHashMap<Integer, T2> machineDataMap = null;
		if (!pageToMachineDataMap.containsKey(page)) {
			machineDataMap = new ConcurrentHashMap<Integer, T2>();
			pageToMachineDataMap.put(page, machineDataMap);
		} else {
			machineDataMap = pageToMachineDataMap.get(page);
		}

		machineDataMap.put(machineid, value);
	}
	
	/**
	 * 單一伺服器維護，告訴在機台列表的人，要倒數計時
	 * @param notify
	 */
	public void onRecvServerShutdownCountdownNotify(SGmtoolServerShutdownCountdownNotify notify){
		SGmtoolServerShutdownCountdownNotifyToClient toClientNotify = new SGmtoolServerShutdownCountdownNotifyToClient();
		toClientNotify.setCountdownTime(notify.getCountdownTime());
		toClientNotify.setCountdownType(notify.getShutdownType().getId());
		
		for (GUID sessionId : getAllSessions()) {
			//跟這個Client說準備關服了
			
			MessageAction.sendMessage(sessionId, MessageTypes.S_GMTOOL_SERVER_SHUTDOWN_COUNTDOWN_NOTIFY_TO_CLIENT, toClientNotify);
			
			//等待一下讓消息送出
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				if (log.isErrorEnabled()){
					log.error(GameUtility.getCurMethodName(), e);
				}
			}
		}
	}
	
	/**
	 * 單一伺服器維護，踢掉在機台列表的人
	 */
	public void onRecvKickAllConnectionNotify() {
		
		for (GUID sessionId : getAllSessions()) {
			//跟這個Client說你被踢了
			MessageAction.sendMessage(sessionId, MessageTypes.S_ACCOUNT_CHARACTER_KICK_BY_SERVER, CloseType.KICK_BY_SERVER_SINGLE_MAINTAIN);
			
			// 等待一下讓消息送出
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				if (log.isErrorEnabled()){
					log.error(GameUtility.getCurMethodName(), e);
				}
			}
			
			//踢人了
			if (ServerProperties.isTargetModule(PlayerCommonInfoManager.getInstance().getGatewayBySessionId(sessionId), ModuleName.H5GATEWAY)) {
				H5ServerOperations.sessionClose(sessionId, CloseType.KICK_BY_SERVER_SINGLE_MAINTAIN, ownModule);
			} else {
				ServerOperations.sessionClose(sessionId, CloseType.KICK_BY_SERVER_SINGLE_MAINTAIN, ownModule);
			}
		}
	}
	
	/**
	 * 取得所有在機台列表的guid，應該有更好的寫法
	 * @return
	 */
	private ConcurrentHashSet<GUID> getAllSessions() {
		ConcurrentHashSet<GUID> sessions = new ConcurrentHashSet<GUID>();
		sessions.addAll(sessionWatchInfoMap.keySet());
//		for (ConcurrentHashMap<Integer, ConcurrentHashSet<GUID>> mp : sessionList.values()) {
//			for (ConcurrentHashSet<GUID> guids : mp.values()) {
//				for (GUID guid : guids) {
//					sessions.add(guid);
//				}
//			}
//		}
		return sessions;
	}

	public Set<String> getHalltype() {
		return simpleHistoryData.keySet();
	}

	public boolean isSendHistoryWorking() {
		return sendHistoryWorking;
	}

	public void setSendHistoryWorking(boolean sendHistoryWorking) {
		this.sendHistoryWorking = sendHistoryWorking;
	}

	public boolean isSendSeatStatusWorking() {
		return sendSeatStatusWorking;
	}

	public void setSendSeatStatusWorking(boolean sendSeatStatusWorking) {
		this.sendSeatStatusWorking = sendSeatStatusWorking;
	}
}
